using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

namespace Linquid
{	
	/// <summary>Empty value.</summary>
	public class Nothing
	{
		/// <summary>Default value of Nothing.</summary>
		public static readonly Nothing Empty = new Nothing();
		private Nothing() {}
	}				
	
	/// <summary>Pair of values.</summary>
	public class Pair<A, B>
	{
		/// <summary>Gets the first value of the pair.</summary>
		public A First {get; private set;}
		/// <summary>Gets the second value of the pair.</summary>
		public B Second {get; private set;}
		
		/// <summary>Constructs the pair given two values.</summary>		
		public Pair(A first, B second)
		{
			First = first;
			Second = second;
		}
	}
	
	/// <summary>
	/// Object representing reactive value of type A. 
	/// </summary>
	public class Source<A>: IEnumerable<A>
	{
		private A LastValue {get; set;}
		private bool HasValue {get; set;}

		/// <summary>
		/// Event raised when the value of the source is changed.
		/// </summary>
		public event Action<A> ValueChanged;
		
		/// <summary>
		/// Sets the value of the source and raises the ValueChanged event.
		/// </summary>
		/// <param name="newValue">Value to set. 
		/// This argument cannot be null.</param>
		public void SetValue(A newValue)
		{
			if (newValue != null)
			{
				if (ValueChanged != null )
				{				
					ValueChanged(newValue);									
				}			
				HasValue = true;
				LastValue = newValue;
			}
			else
			{
				throw new ArgumentNullException(
					"Source's value cannot be set to null");
			}
		}
				
		/// <summary>
		/// Creates a source, which doesn't contain any value.		
		/// </summary>
		public Source() {}

		/// <summary>
		/// Creates a source and sets its value.
		/// </summary>
		/// <param name="init">Initial value of the source.</param>
		public Source(A init): base()
		{
			SetValue(init);			
		}
		
		/// <summary>
		/// Maps the current source with given function.
		/// </summary>
		/// <param name="f">Function used for mapping values.</param>
		/// <returns>Source which value is f(x) when the value of
		/// the current source is x.</returns>
		public Source<B> Map<B>(Func<A, B> f)
		{
			var source = 
				HasValue ?
					new Source<B>(f(LastValue)) :
					new Source<B>();			
			ValueChanged += x => source.SetValue(f(x));
			return source;
		}
		
		/// <summary>
		/// Maps a pair of sources with given function.
		/// </summary>
		/// <param name="other">Source that provides second arguments of f.</param>
		/// <param name="f">Function used for mapping values.</param>
		/// <returns>Source which value is f(x, y) when the value of
		/// the current source is x and the value of the other source is y.</returns>
		public Source<C> Map2<B, C>(Source<B> other, Func<A, B, C> f)
		{			
			var source = 
				(HasValue && other.HasValue) ?
					new Source<C>(f(LastValue, other.LastValue)) :
					new Source<C>();
			
			ValueChanged += x => 
			{
			 	if (other.HasValue)
				{
					source.SetValue(f(x, other.LastValue));
				}			
			};
			
			other.ValueChanged += x => 
			{
				if (HasValue)
				{
					source.SetValue(f(LastValue, x));
				}	
			};
			
			return source;
		}
		
		/// <summary>
		/// Filters the source output.
		/// </summary>
		/// <param name="p">Predicate used to filter values.</param>
		/// <returns>Source which value is set to x only if p(x) is true.</returns>
		public Source<A> Filter(Predicate<A> p)
		{
			var source = 
				(HasValue && p(LastValue)) ?
					new Source<A>(LastValue) :
					new Source<A>();			
		
			ValueChanged += x =>
			{
				if (p(x))
				{
					source.SetValue(x);
				}
			};
			return source;
		}				

		/// <summary>
		/// Both filters and maps source's values.
		/// </summary>
		/// <param name="p">Predicate used for filtering.</param>
		/// <param name="f">Function used for mapping.</param>
		/// <returns>Transformed source.</returns>
		public Source<B> FilterMap<B>(Predicate<A> p, Func<A, B> f)
		{
			return Filter(p).Map(f);
		}
		
		/// <summary>
		/// Delays value change until the value of the other source
		/// is also changed. 
		/// </summary>
		/// <param name="waitSource">Source used for synchronization.
		/// Its values are discarded.</param>
		/// <returns>Source which value changes only only when the value of 'waitSource'
		/// also changes.</returns>
		public Source<A> When<B>(Source<B> waitSource)
		{
			var source = 
				HasValue ? new Source<A>(LastValue) :
						   new Source<A>();
			if (ValueChanged == null)
			{
				ValueChanged += x => {};
			}
			
			waitSource.ValueChanged += x => 
			{
				if (HasValue)
				{
					source.SetValue(LastValue);
				}
			};
			return source;
		}
				
		/// <summary>
		/// Aggregates the values of the current source.
		/// </summary>
		/// <param name="f">Function used for aggregation</param>
		/// <param name="init">Initial argument of f</param>
		/// <returns>Source containing the aggregated value.</returns>
		public Source<B> Fold<B>(Func<A, B, B> f, B init)
		{
			var state = init;
			var source = new Source<B>(state);
			ValueChanged += x =>
			{
				state = f(x, state);
				source.SetValue(state);
			};
			return source;
		}
		
		/// <summary>
		/// Combines the values of two sources.
		/// </summary>
		/// <param name="other">Source used for combination.</param>
		/// <returns>Source which value is the last changed value
		/// of this source or the other source.</returns>
		public Source<A> Join(Source<A> other)
		{
			var source = 
				other.HasValue ? new Source<A>(other.LastValue):
				HasValue ? new Source<A>(LastValue) :
				new Source<A>();
			ValueChanged += x => source.SetValue(x);
			other.ValueChanged += x => source.SetValue(x);
			return source;
		}			
		
		internal static Source<B[]> Collect<B>(Source<B>[] sources)
		{
			var result = new B[sources.Length];
			var source = new Source<B[]>();
			foreach (var i in Enumerable.Range(0, sources.Length))
			{
				sources[i].ValueChanged += x => result[i] = x;
				source.SetValue((B[]) result.Clone());
			}
			return source;
		}
		
		/// <summary>
		/// Replaces source's values by the given constant.
		/// </summary>
		/// <param name="value">Value to replace source's contents with.</param>
		/// <returns>Source which value is always the given constant.</returns>
		public Source<B> Const<B>(B value)
		{
			return Map(_ => value);
		}
		
		/// <summary>
		/// Sums source's values.
		/// </summary>
		/// <param name="toInt">Conversion to apply before addition.</param>
		/// <returns>Source which value is the accumulated sum.</returns>
		public Source<int> Sum(Func<A, int> toInt)
		{
			return Fold((x, sum) => toInt(x) + sum, 0);
		}

		/// <summary>
		/// Computes the maximum of source's values.
		/// </summary>
		/// <param name="toInt">Conversion from source's value 
		/// to comparable number.</param>
		/// <returns>Source which value is the current maximum.</returns>
		public Source<int> Max(Func<A, int> toInt)
		{
			return Fold((x, max) => toInt(x) > max ? toInt(x) : max, int.MinValue);
		}
		
		/// <summary>
		/// Computes the minimum of source's values.
		/// </summary>
		/// <param name="toInt">Conversion from source's value 
		/// to comparable number.</param>
		/// <returns>Source which value is the current minimum.</returns>
		public Source<int> Min(Func<A, int> toInt)
		{
			return Fold((x, min) => toInt(x) < min ? toInt(x) : min, int.MaxValue);
		}
		
		/// <summary>
		/// Counts the number of value changes in the source.
		/// </summary>
		/// <returns>Source which value is the accumulated count.</returns>
		public Source<int> Count()
		{
			return Fold((_, count) => count + 1, 0);
		}

		/// <summary>
		/// Provides access to the current value of the source.
		/// </summary>
		public IEnumerator<A> GetEnumerator()
		{
			if (HasValue)
			{
				yield return LastValue;
			}
		}
		
		IEnumerator IEnumerable.GetEnumerator()
		{
			throw new NotImplementedException();
		}
		
		///<summary>Method used in LINQ queries.</summary>
		public Source<B> Select<B>(Func<A, B> f)
		{
			return Map(f);
		}

		///<summary>Method used in LINQ queries.</summary>
		public Source<A> Where<B>(Func<A, bool> f)
		{			
			return Filter(x => f(x));
		}						
				
		///<summary>Method used in LINQ queries.</summary>
		public Source<A> OrderBy<B>(Func<A, Source<B>> f)
		{			
			return When(f(LastValue));
		}
		
		///<summary>Method used in LINQ queries.</summary>
		public Source<A> ThenBy<B>(Func<A, Source<B>> f)
		{			
			return When(f(LastValue));
		}
		
		///<summary>Method used in LINQ queries.</summary>
		public Source<C> SelectMany<B, C>(Func<A, Source<B>> f,
		                       	          Func<A, B, C> ff)
		{
			return Map2(f(LastValue), ff);
		}
	} 
}

